﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;

namespace Portables.Collections
{
   public class ObservableView<TItem> : IList, INotifyCollectionChanged, INotifyPropertyChanged
   {
      #region private fields

      private readonly List<TItem> _items = new List<TItem>();
      private readonly IObservableCollection<TItem> _source;
      private IComparer<TItem> _sorter;
      private Predicate<TItem> _filter;

      #endregion

      #region constructors

      public ObservableView(IObservableCollection<TItem> source, Func<TItem, TItem, int> sorter, Predicate<TItem> filter = null)
         : this(source, new DelegatedComparer<TItem>(sorter), filter)
      {
      }

      public ObservableView(IObservableCollection<TItem> source, IComparer<TItem> sorter, Predicate<TItem> filter = null)
      {
         if(source == null) throw new ArgumentNullException("source");
         if(sorter == null) throw new ArgumentNullException("sorter");

         _source = source;
         _sorter = sorter;
         _filter = filter ?? (x => true);

         _items.AddRange(GetFilteredSortedSource());

         source.CollectionChanged += OnSourceCollectionChanged;
      }

      #endregion

      #region public properties

      public Predicate<TItem> Filter
      {
         get { return _filter; }
         set
         {
            _filter = value ?? (x => true);
            Reset();
         }
      }

      public IComparer<TItem> Sort
      {
         get { return _sorter; }
         set
         {
            if(value == null) throw new ArgumentNullException("value");
            _sorter = value;
            Reset();
         }
      }

      public IObservableCollection<TItem> Source
      {
         get { return _source; }
      }

      #endregion

      #region protected methdos

      protected IEnumerable<TItem> GetFilteredSortedSource()
      {
         if(_sorter == null && _filter == null)
         {
            return _source;
         }

         if(_sorter == null)
         {
            return _source.Where(s => _filter(s));
         }

         if(_filter == null)
         {
            return _source.OrderBy(s => s, _sorter);
         }

         return _source.Where(s => _filter(s)).OrderBy(s => s, _sorter);
      }

      protected void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
      {
         switch(e.Action)
         {
            case NotifyCollectionChangedAction.Add:
               Add(e.NewItems, e.NewStartingIndex);
               break;
            case NotifyCollectionChangedAction.Remove:
               Remove(e.OldItems, e.OldStartingIndex);
               break;
            case NotifyCollectionChangedAction.Replace:
               Replace(e.NewItems, e.OldItems);
               break;
            case NotifyCollectionChangedAction.Reset:
               Reset();
               break;
         }
      }

      protected void Add(IList added, int startingIndex)
      {
         foreach(var item in added.OfType<TItem>())
         {
            if(_filter(item))
            {
               int index = Add(item);

               //TODO call this for the range, if possible
               OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
            }
         }
      }

      protected int Add(TItem item)
      {
         int index = _items.BinarySearch(item, _sorter);
         if(index < 0)
         {
            index = Math.Abs(index) - 1;
         }
         _items.Insert(index, item);
         return index;
      }

      protected void Remove(IList removed, int startingIndex)
      {
         foreach(var item in removed.OfType<TItem>())
         {
            int index = _items.IndexOf(item);
            if(index >= 0)
            {
               _items.RemoveAt(index);

               //TODO call this for the range, if possible
               OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
            }
         }
      }

      protected void Replace(IList newItems, IList oldItems)
      {
         if(newItems.Count == oldItems.Count)
         {
            for(int i = 0; i < newItems.Count; i++)
            {
               var added = (TItem)newItems[i];
               var removed = (TItem)oldItems[i];
               int index = _items.IndexOf(removed);
               bool passesFilter = _filter(added);
               if(index >= 0 && passesFilter)
               {
                  _items[index] = added;
                  //TODO call this for the range, if possible
                  OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, added, removed, index));
               }
               else if(index >= 0)
               {
                  _items.RemoveAt(index);
                  OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed, index));
               }
               else if(passesFilter)
               {
                  Add(added);
               }
            }
         }
      }

      protected void Reset()
      {
         _items.Clear();
         _items.AddRange(GetFilteredSortedSource());

         OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
      }

      protected void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
      {
         CollectionChanged(this, args);
         if(args.Action != NotifyCollectionChangedAction.Replace)
         {
            PropertyChanged(this, new PropertyChangedEventArgs("Count"));
         }
      }

      #endregion

      #region INotifyCollectionChanged members

      public event NotifyCollectionChangedEventHandler CollectionChanged = delegate { };
      public event PropertyChangedEventHandler PropertyChanged = delegate { };

      #endregion

      #region IList explicit members

      object IList.this[int index]
      {
         get { return _items[index]; }
         set { throw new NotSupportedException(); }
      }

      int ICollection.Count
      {
         get { return _items.Count; }
      }

      bool IList.IsFixedSize
      {
         get { return false; }
      }

      bool IList.IsReadOnly
      {
         get { return false; }
      }

      bool ICollection.IsSynchronized
      {
         get { return false; }
      }

      object ICollection.SyncRoot
      {
         get { return null; }
      }

      int IList.Add(object value)
      {
         throw new NotSupportedException();
      }

      void IList.Clear()
      {
         Reset();
      }

      bool IList.Contains(object value)
      {
         return _items.Contains((TItem)value);
      }

      void ICollection.CopyTo(Array array, int index)
      {
         _items.Cast<object>().ToArray().CopyTo(array, index);
      }

      int IList.IndexOf(object value)
      {
         return _items.IndexOf((TItem)value);
      }

      void IList.Insert(int index, object value)
      {
         throw new NotSupportedException();
      }

      void IList.Remove(object value)
      {
         _items.Remove((TItem)value);
      }

      void IList.RemoveAt(int index)
      {
         _items.RemoveAt(index);
      }

      IEnumerator IEnumerable.GetEnumerator()
      {
         return _items.GetEnumerator();
      }

      #endregion
   }
}